﻿#region Using Directives

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Kongo.Properties;

#endregion Using Directives


namespace Kongo
{
    partial class Template 
    {
        // Helper class that parses the template
        private class Parser
        {
            #region Constants

            private const string EXPRESSION = "expression";
            private const string STATEMENT = "statement";

            #endregion Constants


            #region Fields

            private static readonly Regex blockRegex = new Regex(@"{{\s*(?<expression>.*?)\s*}}|{%\s*(?<statement>.*?)\s*%}|{#.*?#}");
            private static readonly Regex argRegex = new Regex(@"(?:""[^""\\]*(?:\\.[^""\\]*)*""|[^ \t])+");

            private Template template;
            private Stack<Tag> scope;
            private string source;
            private int offset;

            #endregion Fields


            #region Methods

            private void AddLiteral(int endIndex)
            {
                // Create a text node that when rendered will
                // output the literal text of the source
                int length = (endIndex - offset);
                if (length > 0)
                {
                    Literal literal = new Literal(source, offset, length);
                    AddNode(literal);
                }
            }


            private void AddNode(Node node)
            {
                // Here we ask the tag that currently has scope if they want this node.
                // If not, we consider it the end of that scope and walk back up the tree

                Tag parentTag = null;
                while (scope.Count != 0)
                {
                    parentTag = scope.Peek();
                    bool theyWantIt = parentTag.ChildNode(node);
                    if (theyWantIt)
                        return;

                    scope.Pop();
                }

                // Add it to the root node
                template.nodes.Add(node);
            }


            private void AddTag(string statement)
            {
                if (String.IsNullOrEmpty(statement))
                    throw new ArgumentException(Helpers.Format(Resource.Argument_Expected, "Tag"));

                // Get all the args in the statement
                List<string> args = new List<string>();
                foreach (Match m in argRegex.Matches(statement))
                    args.Add(m.Value);

                // Create the tag
                ITagCreator creator = Template.GetTagCreator(args[0]);
                if (creator == null)
                    throw new ArgumentException(Helpers.Format(Resource.Argument_ItemNotFound, args[0], "tag"));

                Tag tag = creator.Create(args.ToArray());
                AddNode(tag);
                if (tag.SupportsChildNodes)
                    scope.Push(tag);
            }


            private void AddVariable(string expression)
            {
                Variable variable = new Variable(expression);
                AddNode(variable);
            }


            public void HitMeBabyOneMoreTime()
            {
                try
                {
                    Run();
                }
                catch (Exception e)
                {
                    throw;
                }
            }


            private void Run()
            {
                Match m = blockRegex.Match(source);
                while (m.Success)
                {
                    // Flush any text between this match and the last match 
                    // (or source start) as a text node
                    AddLiteral(m.Index);
                    offset = m.Index + m.Length;

                    switch (m.Value.Substring(0, 2))
                    {
                        case "{{":
                            AddVariable(m.Groups[EXPRESSION].Value);
                            break;

                        case "{%":
                            AddTag(m.Groups[STATEMENT].Value);
                            break;
                    }

                    m = m.NextMatch();
                }

                // Flush any text between this match and the last match 
                // (or source start) as a text node
                AddLiteral(source.Length);
            }

            #endregion Methods


            #region Constructors

            public Parser(Template template, string source)
            {
                Debug.Assert(template != null, "The 'template' argument was null.");
                Debug.Assert(source != null, "The 'source' argument was null.");

                this.scope = new Stack<Tag>();
                this.template = template;
                this.source = source;
            }

            #endregion Constructors
        }
    }
}
